package com.verisette.ncos.service.accesscontrol.bizunit;

import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;

import org.corp.agi.common.constant.CommonConstant;
import org.corp.agi.common.exception.BusinessException;
import org.corp.agi.common.exception.ProcessException;
import org.corp.agi.common.exception.TechnicalException;
import org.corp.agi.service.bean.ProcessContext;
import org.corp.agi.service.bean.UserBean;
import org.corp.agi.service.bizunit.BusinessUnit;
import org.corp.agi.service.bizunit.CommonBusinessUnit;
import org.corp.common.util.security.Encrypter;
import com.verisette.ncos.common.ProjectConstant;
import com.verisette.ncos.persistence.dao.AccessControlDAO;
import com.verisette.ncos.persistence.domain.CustomMasterUserGroup;
//import com.verisette.ncos.persistence.domain.LogUserAccess;
import com.verisette.ncos.persistence.domain.MasterObject;
import com.verisette.ncos.persistence.domain.MasterUserProfile;

public class CustomAuthenticateBU  extends CommonBusinessUnit {
	
	private Logger logger = Logger.getLogger(CustomAuthenticateBU.class);
	
	private Encrypter encrypter;	
	@Autowired
	public void setEncrypter(Encrypter encrypter) {
		this.encrypter = encrypter;
	}
	
	private AccessControlDAO accessControlDAO;
	@Autowired
	@Required
	public void setAccessControlDAO(AccessControlDAO accessControlDAO) {
		this.accessControlDAO = accessControlDAO;
	}
	
	private BusinessUnit insertAccessLogBU;
	@Autowired
	@Required
	public void setInsertAccessLogBU(BusinessUnit insertAccessLogBU) {
		this.insertAccessLogBU = insertAccessLogBU;
	}
	
	private BusinessUnit updateAccessLogBU;
	@Autowired
	@Required
	public void setUpdateAccessLogBU(BusinessUnit updateAccessLogBU) {
		this.updateAccessLogBU = updateAccessLogBU;
	}


	/**
	 * @throws ProcessException 
	 * @req : <N/A>
	 * @description : <N/A>
	 *
	 */
	public void execute(ProcessContext processContext) throws ProcessException {
		
		Map<String, Object> inputMap = (Map<String, Object>)processContext.getValueObject();
		
		UserBean loginUser = (UserBean) inputMap.get(ProjectConstant.USER_PROFILE);
		String ipAddress = (String)inputMap.get(ProjectConstant.USER_PROFILE_IP_ADDRESS);
		
		
		//  Make sure lowercase loginname
		loginUser.setUsername( loginUser.getUsername().toLowerCase() );
						
		String encryptPassword;
		String encryptDefaultPassword;
		try {
			if( encrypter != null ){
				encryptPassword = encrypter.encrypt(loginUser.getPassword());	
				encryptDefaultPassword = encrypter.encrypt(ProjectConstant.DEFAULT_PASSWORD);	
			}
			else{ //If no encrypter is configed -> use plain password 
				encryptPassword = loginUser.getPassword();
				encryptDefaultPassword = ProjectConstant.DEFAULT_PASSWORD;
			}
			
			loginUser.setPassword( encryptPassword );
			
		} catch (NoSuchAlgorithmException e) {
			logger.error("Error while encrypting password", e);
			throw new TechnicalException( CommonConstant.MESSAGE_CODE_AGI0001, e);
		}

		

		//  (1) Check username is correct
		List<MasterUserProfile> masUsers = (List<MasterUserProfile>) commonDAO.findByProperty(MasterUserProfile.class, "uspUserName", loginUser.getUsername()); 	
		if( masUsers == null || masUsers.size() != 1 ){
			logger.info("User ["+loginUser.getUsername()+"] is not found.");	//User is not found
			throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0003);
		}
		
		
		//................................................................//
		
		
		//  (2) Check password is correct
		MasterUserProfile authenUser = (MasterUserProfile) masUsers.get(0);

		if( !loginUser.getPassword().equals( authenUser.getUspPassword() ) ){
			logger.info("Password for user ["+loginUser.getUsername()+"] is invalid.");
			// Check incorrect password > 3 times
			int wrongLoginCounter = authenUser.getUspWrongLoginCounter()+1;
			if( wrongLoginCounter > Integer.parseInt(ProjectConstant.MAXIMUM_WRONG_LOGIN_COUNTER) ){
				// Call update wrong login counter
				authenUser.setUspLockFlag(ProjectConstant.Y_FLAG);
				processContext.setValueObject(authenUser);
				updateAccessLogBU.execute(processContext);
				
				// Call insert access log BU (Account is locked)
				Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
				inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_ACCOUNT_LOCKED);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
				processContext.setValueObject(inputAccessLogMap);
				insertAccessLogBU.execute(processContext);
				
				logger.info("Account is locked.");
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0011);
			}else{
				// Call update wrong login counter
				processContext.setValueObject(authenUser);
				updateAccessLogBU.execute(processContext);
				
				// Call insert access log BU (Input incorrect password)
				Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
				inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_INPUT_INCORRECT_PASSWORD);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
				processContext.setValueObject(inputAccessLogMap);
				insertAccessLogBU.execute(processContext);
							
				logger.info("Input incorrect password.");
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0003);	
			}
		} 
		
		
		//................................................................//
		

		// (4) Check group status inactive
		List<CustomMasterUserGroup> listUserGroup = accessControlDAO.listUserGroupStatus( authenUser.getUspUserName() );
		if(listUserGroup.size()!=0){
			Map usgStatusMap = new HashMap<String, String>();
			// loop put status to map
			for (CustomMasterUserGroup userGroup : listUserGroup) {
				usgStatusMap.put(userGroup.getUsgName(), userGroup.getUsgStatus());
			}
			// check group status inactive
			if( usgStatusMap.containsValue(ProjectConstant.INACTIVE_FLAG) ){
				logger.info("User group inactive.");	//User's group status is inactive
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0007);
			}
		}
		
		
		//................................................................//
		
		
		// (5) Check user status inactive & throw error msg#2
		if( !authenUser.getUspStatus().equals(ProjectConstant.ACTIVE_FLAG) ){
			// Check incorrect password > 3 times
			int wrongLoginCounter = authenUser.getUspWrongLoginCounter()+1;
			if( wrongLoginCounter > Integer.parseInt(ProjectConstant.MAXIMUM_WRONG_LOGIN_COUNTER) ){
				// Call insert access log BU (Inactive user try to login)
				Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
				inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_INACTIVE_USER_TRY_TO_LOGIN);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
				processContext.setValueObject(inputAccessLogMap);
				insertAccessLogBU.execute(processContext);
				
				
				logger.info("Inactive user try to login.");
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0011);
			}else{
				logger.info("User status is inactive.");	//User status is inactive
				// Call update wrong login counter
				processContext.setValueObject(authenUser);
				updateAccessLogBU.execute(processContext);
				
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0004);
			}
		}
		
		//................................................................//
		
		// check user lock is Y cannot login
		if(ProjectConstant.Y_FLAG.equals(authenUser.getUspLockFlag())){
			Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
			inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_USER_LOCK);
			inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
			inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
			processContext.setValueObject(inputAccessLogMap);
			insertAccessLogBU.execute(processContext);
			
			logger.info("User Lock try to login");
			throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0004);
		}
		
		//................................................................//
		
		
		// (6) Access Logging (Login complete)
		// Call insert access log BU (Login complete)
		Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
		inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_LOGIN_COMPLETE);
		inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
		inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
		processContext.setValueObject(inputAccessLogMap);
		insertAccessLogBU.execute(processContext);
		this.updateLastAccessDate(authenUser);
		
		this.resetLoginFailCounter( authenUser );	// Reset login fail counter
		
		
		//................................................................//
		
		
		// Set privilege to User
		Set<String> privileges = new HashSet<String>();			
		
		
		//  (3) Check First time or Pwd Expire or reset password
		//TODO แยก case
		ArrayList checkResult = new ArrayList<Boolean>();
		
		if(loginUser.getPassword().equals( encryptDefaultPassword )){
			logger.info("First time to login.");
			checkResult.add(true);
		}
		if(null!=authenUser.getUspExpireDate()){
			if(this.checkPasswordExpire(authenUser.getUspExpireDate())){
				logger.info("Password expired.");
				checkResult.add(true);
			}
		}
		if(null!=authenUser.getUspForceChngPwdFlag()){
			if(authenUser.getUspForceChngPwdFlag().equals(ProjectConstant.Y_FLAG)){
				logger.info("Force to change password.");
				checkResult.add(true);
			}
		}
		
		if(checkResult.size()!=0){
			if(checkResult.contains(true)){
				logger.info("First time to login or Password expired or Force to change password.");

				// Add privilege change password
				privileges.add( ProjectConstant.CHANGE_PASSWORD_OBJ_ID );
				processContext.addMessage(ProjectConstant.CHANGE_PASSWORD_FORCE_TO_CHANE_PWD_FLAG);
				
				//................................................................//
			
			}
			
		}else{
			
			List<MasterObject> objects = accessControlDAO.listPrivilege(loginUser.getUsername());
			if( objects == null || objects.size() == 0 ){
				//User is not found
				logger.info("User ["+loginUser.getUsername()+"] have not authorized.");
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0006);
			}
			
			authenUser.setRole(loginUser.getRole());				
			
			for( MasterObject obj : objects ){
				privileges.add( obj.getObjId() );
			}
		}
		
		authenUser.setPrivileges(privileges);								
		
		processContext.setValueObject(authenUser);
		
	}
	

	// Check password expire
	private boolean checkPasswordExpire(Timestamp expireDate){
		boolean result = false;
		if(null!=expireDate){
			Calendar cal = Calendar.getInstance();
			Timestamp nowTimestamp = new Timestamp(cal.getTimeInMillis());
			//Timestamp expireTimestamp = expireDate;
			if( (expireDate.before(nowTimestamp))||(expireDate.equals(nowTimestamp)) ){
				result = true;
				logger.debug("Check password expire is True.");
			}
		}
		return result;
	}
	
	
	// Reset login fail counter
	private void resetLoginFailCounter(MasterUserProfile authenUser){
		authenUser.setUspWrongLoginCounter( Short.parseShort(ProjectConstant.DEFAULT_WRONG_LOGIN_COUNTER) );
		commonDAO.update(authenUser);
	}
	
	private void updateLastAccessDate(MasterUserProfile authenUser){
		logger.debug("Update LastAccessDate into MASTER_USER_PROFILE.");
		MasterUserProfile updateAuthenUser = (MasterUserProfile) commonDAO.findById(MasterUserProfile.class, authenUser.getUspId());
		updateAuthenUser.setUspLastAccessDate(new Timestamp(new Date().getTime()));
		commonDAO.update(updateAuthenUser);
	
	}
	
}
